﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SimpleData.ObjectModel.Rules;
using SimpleData.Storage.Common;
//using System.Reactive.Linq;
using System.Diagnostics.Contracts;
using SimpleData.ObjectModel.Design;
using SimpleData.Infrastructure.Provider;
using SimpleData.ObjectModel.Internals;

namespace SimpleData.ObjectModel
{
	/// <summary>
	/// Realiza o gerenciamento dos documentos
	/// </summary>
	public class DocumentManager
	{
		private IProvider<FieldTypeName, FieldTypeDescriptor> typeProvider;
		private IDocumentValidatorFactory validatorFactory;


		public event EventHandler<DocumentEventArgs> ValidatingDocument;
		public event EventHandler<DocumentEventArgs> DocumentValidated;


		public DocumentManager( IStorageConnection connection )
			: this( connection, null, null )
		{ }

		public DocumentManager( IStorageConnection connection, IObjectModelRepository repository )
			: this( connection, null, DocumentManager.CreateDefaultValidator( repository ) )
		{ }

		public DocumentManager( IStorageConnection connection, IProvider<FieldTypeName, FieldTypeDescriptor> typeProvider, IDocumentValidatorFactory validatorFactory )
		{
			Contract.Requires<ArgumentNullException>( connection != null, "connection" );

			this.Connection = connection;

			this.typeProvider = typeProvider ?? Runtime.FieldTypes;
			this.validatorFactory = validatorFactory ?? DocumentManager.CreateDefaultValidator();
		}

		/// <summary>
		/// Obtém ou define a conexão com o banco de dados
		/// </summary>
		public IStorageConnection Connection
		{ get; private set; }

		/// <summary>
		/// Obtém ou define a factory de criação dos tipos de cada campo do sistema
		/// </summary>
		public IProvider<FieldTypeName, FieldTypeDescriptor> TypeProvider
		{
			get { return this.typeProvider; }
			set
			{
				Contract.Requires<ArgumentNullException>( value == null, "TypeProvider" );
				this.typeProvider = value;
			}
		}

		//public ValidatorData Validators
		//{ get; private set; }

		public object Delete( DocumentToken token )
		{
			// Executar os passos parecidos do Save()
			return null;
		}

		public Document GetDocument( DocumentToken token )
		{
			return null;
		}

		/// <summary>
		/// Obtém ou define o objeto que irá criar o validador para os documentos
		/// </summary>
		public IDocumentValidatorFactory ValidatorFactory
		{
			get { return this.validatorFactory; }
			set
			{
				Contract.Requires<ArgumentNullException>( value == null, "ValidatorFactory" );
				this.validatorFactory = value;
			}
		}


		public DocumentValidationResult Save( Document doc )
		{
			// 1. Dispara o evento BeforeValidate
			// 2. Valida o documento de acordo com as rules do schema
			// 2.1. Executa as regras associadas ao documento
			// 3. Dispara o evento AfterValidate
			// 4. Dispara o evento BeforeSave
			// 4.1. Cria a lista de comandos que serão executados para alterar/adicionar o documento
			// 4.2. Executa o fluxo associado a esse Schema (se necessário pode alterar o documento e a lista de comandos)
			// 5. Salva os dados
			// 5.1. Coloca na fila para fazer o log da alteração e caso ocorra um erro fazer o rollback
			// 5.2. Salvo no banco de dados
			// 5.3. Atualiza os índices
			// 6. Dispara o evento AfterSave
			// 6.1. Por evento, atualiza as views

			//IDocumentValidator validator;
			DocumentValidationResult result = new DocumentValidationResult( doc );
			//DocumentContext context = new DocumentContext( doc, result );
			DocumentValidatorContext context = new DocumentValidatorContext( doc );
			// Obter a instância do schema

			// TODO : Utilizar o padrão "Chain of Resposability" para validar e salvar o documento
			// permitindo que seja personalizado a sequencia de comandos

			// TODO : Utilizar comando para salvar e realizar alterações na plataforma

			// Realiza a validação
			this.ValidateStage( context );

			if ( !result.IsValid )
				return result;

			// Salva os dados do documento
			this.SaveDocumentStage( context );

			// Realiza a validação
			//if ( doc.IsNew )
			//    validator = this.Validators.OnCreate.Create( doc.Token.SchemaName );
			//else
			//    validator = this.Validators.OnUpdate.Create( doc.Token.SchemaName );

			// Salva
			// this.Connection.InsertOrUpdate(


			// Retorna o resultado da operação
			return null;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		protected virtual void ValidateStage( DocumentValidatorContext context )
		{
			Document doc = context.Document;

			this.OnValidatingDocument( new DocumentEventArgs( context ) );

			if ( context.Result.IsValid )
			{
				//
				IDocumentValidator validator = this.ValidatorFactory.Create( doc.Schema );
				validator.Validate( new DocumentValidatorContext( doc ) );

				this.OnDocumentValidated( new DocumentEventArgs( context ) );
			}
		}

		protected virtual void SaveDocumentStage( DocumentContext context )
		{
			Document doc = context.Document;
			Schema schema = null;

			DataObjectRow row = new DataObjectRow( new DataObjectColumnKey( doc.Token ), doc.Schema );
			foreach ( var field in schema.Fields )
			{
				var name = field.Name;
				object value;

				if ( field.Storage && doc.Values.TryGetValue( name, out value ) )
				{
					if ( value != null )
					{
						var fieldType = this.TypeProvider.Create( field.TypeName );
						var converter = fieldType.CreateConverter( field );

						// Adiciona o valor
						row.AddColumn( name, converter.Serialize( value ) );
					}
				}
			}

			this.Connection.InsertOrUpdate( row );
		}

		protected virtual void OnValidatingDocument( DocumentEventArgs args )
		{
			if ( this.ValidatingDocument != null )
				this.ValidatingDocument( this, args );
		}

		protected virtual void OnDocumentValidated( DocumentEventArgs args )
		{
			if ( this.DocumentValidated != null )
				this.DocumentValidated( this, args );
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		private static IDocumentValidatorFactory CreateDefaultValidator()
		{
			return DocumentManager.CreateDefaultValidator( null );
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="repository"></param>
		/// <returns></returns>
		private static IDocumentValidatorFactory CreateDefaultValidator( IObjectModelRepository repository )
		{
			if ( repository == null )
				repository = Runtime.Repositories.ObjectModel.Create();

			return new DataObjectValidatorCompiler( Runtime.Rules, repository );
		}
	}

	/// <summary>
	/// 
	/// </summary>
	public class DocumentEventArgs : EventArgs
	{
		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		public DocumentEventArgs( DocumentContext context )
		{
			this.Context = context;
		}

		/// <summary>
		/// 
		/// </summary>
		public DocumentContext Context
		{
			get;
			private set;
		}
	}
}
